// Honor Code: (O_O)
// 我真诚地保证：
// 我自己独立地完成了整个程序从分析、设计到编码的所有工作。
// 如果在上述过程中，我遇到了什么困难而求教于人，那么，我将在程序实习报告中
// 详细地列举我所遇到的问题，以及别人给我的提示。
// 我的程序里中凡是引用到其他程序或文档之处，
// 例如教材、课堂笔记、网上的源代码以及其他参考书上的代码段,
// 我都已经在程序的注释里很清楚地注明了引用的出处。
// 我从未抄袭过别人的程序，也没有盗用别人的程序，
// 不管是修改式的抄袭还是原封不动的抄袭。
// 我编写这个程序，从来没有想过要去破坏或妨碍其他计算机系统的正常运转。

/********************************************
 *  A simple text editor                    *
 *  Author: Leedy (LeedyPKU@gmail.com)      *
 *******************************************/

#include <curses.h>
#include <signal.h>
#include <fstream>
#include <vector>
#include <string>

using namespace std;

vector<string> contents;
int ch, line_y, line_x, top_line, bottom_line;
bool file_has_change = false;
string message = "", search_partten = "";
char *filename = NULL;

// "private" global variables for move()
int y, x, _move_line_y, _move_line_x;

void init();
void move();
void print();
void print_info();
void sig_int(int);
void search();
int sunday_search(string, string, int);

int sunday_search(string text, string pattern, int pos = 0)
{
    int p_length = (int) pattern.length();
    int t_length = (int) text.length();
    int i = 0, j = pos;
    while (j + p_length <= t_length) {
        for (i = 1; i <= p_length; i++) {
            if (pattern[p_length - i] != text[j + p_length - i]) {
                break;
            }
        }
        if (i <= p_length) {
            for (i = 1; i <= p_length; i++) {
                if (pattern[p_length - i] == text[j + p_length]) {
                    break;
                }
            }
            j += i;
        } else {
            return j;
        }
    }
    return -1;
}

// Initialization function
void init()
{
    signal(SIGINT, &sig_int);
	initscr();
	cbreak();
	nonl();
	noecho();
	intrflush(stdscr,FALSE);
	keypad(stdscr,TRUE);
	refresh();
}

string search()
{
	bool search_ok = false, new_search = true;
	const vector<string>::iterator current_it = contents.begin() + line_y;
	while (true) {
		attron(A_REVERSE);
		move(LINES - 1, 0);
		hline('\t', 999999);
		mvaddstr(LINES-1, 0, " Search: ");
		attron(A_UNDERLINE);
		addstr(search_partten.data());
		attroff(A_UNDERLINE);
		attroff(A_REVERSE);
		ch = getch();
		switch (ch) {
			case 127:
			case KEY_BACKSPACE:
				if (search_partten.length()) {
					search_partten.erase(search_partten.end() - 1);
				}
				break;
			case 13:
			case KEY_ENTER:
				search_ok = false;
				for (vector<string>::iterator it = current_it; it != current_it || new_search == true;) {
					int pos;
					if (new_search) {
						new_search = false;
						// pos = it->find(search_partten, line_x + 1);
						pos = sunday_search(*it, search_partten, line_x + 1);
					} else {
						// pos = it->find(search_partten);
						pos = sunday_search(*it, search_partten);
					}
					if (pos >= 0) {
						line_x = pos;
						line_y = it - contents.begin();
						search_ok = true;
						break;
					}
					it++;
					if (it == contents.end()) {
						it = contents.begin();
					}
				}
				if (search_ok) {
					return "Search OK: " + search_partten;
				} else {
					return "Not found: " + search_partten;
				}
				break;
			default:
				search_partten.append(1, ch);
		}
	}
}

// This function convert (line_y, line_x) to (y, x)
// Called each time line_y or line_x change
void move()
{
	if (line_y > bottom_line) {
		if (line_y < (int) contents.size()) {
			top_line++;
			print();
			return;
		}
		line_y = contents.size() - 1;
		beep();
	} else if (line_y < top_line) {
		if (line_y >= 0) {
			top_line = line_y;
			print();
			return;
		}
		line_y = 0;
		beep();
	}
	if (line_x < 0) {
		line_x = 0;
		return;
	} else if (line_x > (int) contents[line_y].size()) {
		line_x = contents[line_y].size();
	}
	if (_move_line_y != line_y) {
		y = 0;
		for (int i = top_line; i < line_y; ++i) {
			int lines = (contents[i].size() + COLS - 1) / COLS;
			if (lines == 0) {
				lines = 1;
			}
			y += lines;
		}
	} else {
		y -= _move_line_x / COLS;
	}
	y += line_x / COLS;
	x = line_x % COLS;
	_move_line_y = line_y;
	_move_line_x = line_x;
	move(y, x);
}

// Print the info at the bottom
void print_info()
{
	attron(A_REVERSE);
	move(LINES - 1, 0);
	hline('\t', 999999);
	if (message.length()) {
		mvprintw(LINES-1, 1, message.data());
		message.erase(0);
	} else {
		mvprintw(LINES-1, COLS-20, "LINE=%d  COL=%d", line_y, line_x);
	}
	attroff(A_REVERSE);
	move();
}

// Re-paint the whole screen
void print()
{
	clear();
	move(0, 0);
	int lines, rest_lines = LINES - 1, this_line;
	for (this_line = top_line; this_line < (int) contents.size(); ++this_line) {
		lines = (contents[this_line].size() + COLS - 1) / COLS;
		if (lines == 0) {
			lines = 1;
		}
		rest_lines -= lines;
		if (rest_lines < 0) {
			break;
		}
		addstr(contents[this_line].data());
		if (contents[this_line].size() == 0 || contents[this_line].size() % COLS) {
			addstr("\n");
		}
	}
	bottom_line = this_line - 1;
	print_info();
}

// SIGINT cache function
void sig_int(int sig)
{
    signal(SIGINT, SIG_DFL);
	if (filename && file_has_change) {
		message = "Save file? (y/n)";
		print();
		ch = getch();
		if (ch == 'y') {
			ofstream ofile(filename);
			if (ofile.is_open()) {
				for (vector<string>::iterator it = contents.begin(); it != contents.end(); ++it) {
					ofile << *it << endl;
				}
			} else {
				message = "Error, cannot write " + string(filename);
				print();
			}
			ofile.close();
		}
	}
	endwin();
	exit(0);
}

int main(int args, char* argv[])
{
	ifstream file;
	switch (args) {
		case 1:
			message = "No filename specified, using Demo mode.";
			contents.push_back("Demo mode:");
			contents.push_back("");
			contents.push_back("This is a simple text editor");
			contents.push_back("");
			contents.push_back("Usage:");
			contents.push_back("    Ctrl-F: Search");
			contents.push_back("    Ctrl-C: Exit");
			break;
		case 2:
			file.open(argv[1]);
			if (!file.is_open()) {
				printf("cannot open file %s\n", argv[1]);
				exit(-1);
			}
			filename = argv[1];
			while (file) {
				string temp;
				getline(file, temp);
				contents.push_back(temp);
			}
			file.close();
			contents.pop_back();
			break;
		default: 
			printf("Usage: %s [<filename>]\n", argv[0]);
			exit(1);
	}
	init();
	print();
	getyx(stdscr, y, x);
	move(y, x);
	while (true) {
		ch = getch();
		switch (ch) {
			case -1:		// window resize
				break;
			case KEY_UP:
				line_y--;
				break;
			case KEY_DOWN:
				line_y++;
				break;
			case KEY_RIGHT:
				line_x++;
				break;
			case KEY_LEFT:
				line_x--;
				break;
			case 330:		// delete key
				file_has_change = true;
				if (line_x == (int) contents[line_y].size()) {
					if (line_y == (int) contents.size()) {
						break;
					}
					contents[line_y].append(contents[line_y + 1]);
					contents.erase(contents.begin() + line_y + 1);
				} else {
					contents[line_y].erase(line_x, 1);
				}
				break;
			case 127:		// backspace key
				file_has_change = true;
				if (line_x == 0) {
					if (line_y == 0) {
						break;
					}
					line_x = contents[line_y - 1].size();
					contents[line_y - 1].append(contents[line_y]);
					contents.erase(contents.begin() + line_y);
					line_y--;
				} else {
					contents[line_y].erase(line_x - 1, 1);
					line_x--;
				}
				break;
			case 13:		// enter key
				file_has_change = true;
				contents.insert(contents.begin() + line_y + 1, contents[line_y].substr(line_x));
				contents[line_y].erase(line_x);
				line_y++;
				line_x = 0;
				break;
			case 6:			// Ctrl-F
				message = search();
				break;
			default:
				file_has_change = true;
				contents[line_y].insert(line_x, 1, ch);
				line_x++;
		}
		print();
	}
	endwin();
	return 0;
}
